#!/usr/bin/php -q
<?PHP
/* Copyright 2005-2025, Lime Technology
 * Copyright 2012-2025, Bergware International.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License version 2,
 * as published by the Free Software Foundation.
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 */
?>
<?
$docroot = '/usr/local/emhttp';
$varroot = '/var/local/emhttp';
$stamps  = '/var/tmp/stamps.ini';
$resync  = '/var/tmp/resync.ini';
$md5_old = -1;

require_once "$docroot/webGui/include/Helpers.php";
require_once "$docroot/webGui/include/publish.php";
extract(parse_plugin_cfg('dynamix',true));

// add translations
$_SERVER['REQUEST_URI'] = 'dashboard/main';
$login_locale = _var($display,'locale');
require_once "$docroot/webGui/include/Translations.php";

// remember current language
$locale_init = $locale;

function my_power($device) {
  global $display;
  if (!_var($display,'power')) return '';
  $number = _var($display,'number','.,');
  [$power,$state] = my_explode('W ', get_nvme_info($device,'state'));
  $power = $power ?: 0;
  $state = $state ? strtr($state, ['non-operational' => 'green', 'operational' => 'orange']) : '';
  return " / <span class='$state'>".number_format($power, $power < 10 ? 2 : 1, $number[0]).' '._('W')."</span>";
}

function my_clock($time) {
  if (!$time) return _('less than a minute');
  $days = floor($time/1440);
  $hour = floor($time/60)%24;
  $mins = $time%60;
  return plus($days,'day',($hour|$mins)==0).plus($hour,'hour',$mins==0).plus($mins,'minute',true);
}

function find_day($D) {
  global $days;
  if ($days[0] == '*') return $D;
  foreach ($days as $d) if ($d >= $D) return $d;
  return $days[0];
}

function find_month($M) {
  global $months, $Y;
  if ($M > 12) {$M = 1; $Y++;}
  if ($months[0] == '*') return $M;
  foreach ($months as $m) if ($m >= $M) return $m;
  return $months[0];
}

function today($D) {
  global $days, $M, $Y;
  if ($days[0]=='*') return date('w',mktime(0,0,0,$M,$D,$Y));
  for ($d = $D; $d < $D+7; $d++) {
    $day = date('w',mktime(0,0,0,$M,$d,$Y));
    if (in_array($day,$days)) return $day;
  }
}

function next_day($D) {
  return find_day(($D+1)%7);
}

function last_day() {
  global $M, $Y;
  return date('t',mktime(0,0,0,$M,1,$Y));
}

function mkdate($D, $s) {
  global $M, $Y;
  if ($s > last_day()) {$s = 1; $M = find_month($M+1);}
  for ($d = $s; $d < $s+7; $d++) if ($D == date('w',mktime(0,0,0,$M,$d,$Y))) return $d;
}

function stage($i) {
  global $h, $m, $D, $M, $Y, $time, $now;
  if ($i < 0) {
    $d = $now ? $D : today(1);
    $s = $now ? date('j',$time) : 1;
    $D = mkdate($d, $s);
    $t = mktime($h,$m,0,$M,$D,$Y)-$time; // first day
    if ($t < 0) {
      $D = mkdate(next_day($d), $s+1);
      $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next day
    }
    if ($t < 0) {
      $s += 7;
      if ($s > last_day()) {
        $s -= last_day();
        $M = find_month($M+1);
      }
      $D = mkdate(today($d), $s);
      $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next week
    }
  } else {
    $d = $i ? ($now ? $D : today($i)) : today(last_day()-6);
    $s = $i ?: last_day()-6;
    $D = mkdate($d, $s);
    $t = mktime($h,$m,0,$M,$D,$Y)-$time; // first day
    if ($t < 0) {
      $D = mkdate(next_day($d), $s);
      $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next day
    }
    if ($t < 0) {
      $M = find_month($M+1);
      $s = $i ?: last_day()-6;
      $D = mkdate(today($s), $s);
      $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next month
    }
    if ($t < 0) {
      $Y++;
      $M = find_month(1);
      $s = $i ?: last_day()-6;
      $D = mkdate(today($s), $s);
      $t = mktime($h,$m,0,$M,$D,$Y)-$time; // next year
    }
  }
  return $t;
}

function normalize($type,$count) {
  $words = explode('_',$type);
  foreach ($words as &$word) $word = $word==strtoupper($word) ? $word : preg_replace(['/^(ct|cnt)$/','/^blk$/'],['count','block'],strtolower($word));
  return ucfirst(implode(' ',$words)).": ".str_replace('_',' ',strtolower($count))."\n";
}

function active_disks($disk) {
  return substr(_var($disk,'status'),0,7)!='DISK_NP' && in_array(_var($disk,'type'),['Parity','Data']);
}

function device_name(&$disk) {
  switch (_var($disk,'type')) {
    case 'Extra' :
    case 'Parity': $type = _var($disk,'rotational') ? 'disk' : 'nvme'; break;
    case 'Data'  :
    case 'Cache' : $type = _var($disk,'rotational') ? (_var($disk,'luksState') ? 'disk-encrypted' : 'disk') : 'nvme'; break;
  }
  $name = _var($disk,'name');
  $fancy = _(my_disk(native($name,1)),3);
  return "<i class='icon-$type f14'></i> <a href=\"".htmlspecialchars("/Dashboard/Main/Settings/Device?name=$name")."\" title=\"$fancy settings\">$fancy</a>";
}

function yellow_text($disk) {
  global $var;
  if (_var($disk, 'type')=='Parity') {
    $text = _var($var,'mdResync')==0 ? 'invalid' : 'syncing';
  } elseif (_var($disk, 'type')=='Data') {
    $text = _var($var,'mdResync')==0 ? 'emulated' : 'rebuilding';
  } else {
    $text = 'invalid';
  }
  return $text;
}

function device_status(&$disk, &$error, &$warning) {
  global $var;
  if (_var($disk,'type')!='Extra' && _var($var,'fsState')=='Stopped') {
    $color = 'green'; $text = 'off-line';
  } else switch (_var($disk,'color')) {
    case 'green-on'    : $color = 'green';  $text = 'active';     break;
    case 'green-blink' : $color = 'grey';   $text = 'standby';    break;
    case 'blue-on'     : $color = 'blue';   $text = 'unassigned'; break;
    case 'blue-blink'  : $color = 'grey';   $text = 'unassigned'; break;
    case 'yellow-on'   : $color = 'yellow'; $text =  yellow_text($disk); $warning++; break;
    case 'yellow-blink': $color = 'grey';   $text =  yellow_text($disk); $warning++; break;
    case 'red-on'      : $color = 'red';    $text = 'disabled';   $error++; break;
    case 'red-blink'   : $color = 'grey';   $text = 'disabled';   $error++; break;
    case 'red-off'     : $color = 'red';    $text = 'faulty';     $error++; break;
    case 'grey-off'    : $color = 'grey';   $text = 'no device';  break;
  }
  return "<i class='fa fa-circle orb $color-orb middle'></i>"._($text);
}

function device_temp(&$disk, &$red, &$orange) {
  global $display;
  $spin = strpos(_var($disk,'color'),'blink')===false;
  $temp = _var($disk,'temp');
  $dev  = _var($disk,'device');
  $nvme = _var($disk,'transport')=='nvme';
  [$hotNVME,$maxNVME] = $nvme ? get_nvme_info($dev,'temp') : [-1,-1];
  $hot  = _var($disk,'hotTemp',-1)>=0 ? $disk['hotTemp'] : ($hotNVME>=0 ? $hotNVME : (_var($disk,'rotational',1)==0 && $display['hotssd']>=0 ? $display['hotssd'] : $display['hot']));
  $max  = _var($disk,'maxTemp',-1)>=0 ? $disk['maxTemp'] : ($maxNVME>=0 ? $maxNVME : (_var($disk,'rotational',1)==0 && $display['maxssd']>=0 ? $display['maxssd'] : $display['max']));
  $top  = $display['top'] ?? 120;
  $color= 'green';
  if (exceed($temp,$max,$top) || (is_numeric($temp) && $temp<0)) {
    $color = 'red'; $red++;
  } elseif (exceed($temp,$hot,$top)) {
    $color = 'orange'; $orange++;
  }
  return ($spin ? "<span class='$color-text'>".my_temp($temp)."</span>" : "*").($nvme ? my_power($dev) : "");
}

function device_smart(&$disk, &$fail, &$smart) {
  global $numbers,$saved;
  if (!_var($disk,'device') || strpos(_var($disk,'color'),'blink')!==false) return "-";
  $failed = ['FAILED','NOK'];
  $name   = _var($disk,'name');
  $named  = no_tilde($name);
  $select = get_value($name,'smSelect',0);
  $level  = get_value($name,'smLevel',1);
  $events = explode('|',get_value($disk,'smEvents',$numbers));
  $title  = '';
  $thumb  = 'thumbs-o-up';
  $text   = _('healthy');
  $color  = 'green';
  $file   = "state/smart/$name";
  $ssa    = file_exists($file) ? exec("grep -Pom1 '^SMART.+: \K[A-Z]+' ".escapeshellarg($file)) : "";
  if (in_array($ssa,$failed)) {
    $title = _('SMART health-check failed')."\n"; $thumb = 'thumbs-o-down'; $color = 'red'; $text = 'fail'; $fail++;
  } else {
    if (empty($saved["smart"]["$named.ack"])) {
      exec("awk 'NR>7{print $1,$2,$4,$6,$9,$10}' ".escapeshellarg($file)." 2>/dev/null", $codes);
      foreach ($codes as $code) {
        if (!$code || !is_numeric($code[0])) continue;
        [$id,$class,$value,$thres,$when,$raw] = my_explode(' ',$code,7);
        $failing = strpos($when,'FAILING_NOW')!==false;
        if (!$failing && !in_array($id,$events)) continue;
        if ($failing || ($select ? $thres>0 && $value<=$thres*$level : $raw>0)) $title .= normalize($class,$failing?$when:$raw);
      }
      if ($title) {$thumb = 'thumbs-o-down'; $color = 'orange'; $text = _('error'); $smart++;} else $title = _('No errors reported')."\n";
    }
  }
  $title .= _('Click for context menu');
  return "<span id='smart-$named' name=Device class='fa fa-$thumb $color-text' style='margin-right:8px' onmouseover='this.style.cursor=\"pointer\"' title='$title'></span><span id='text-$named'>$text</span>";
}

function device_usage(&$disk, &$full, &$high) {
  global $display;
  $text = $display['text'];
  $used = (_var($disk,'type')!='Parity' && _var($disk,'type')!='Extra' && _var($disk,'fsStatus')=='Mounted') ? (_var($disk,'fsSize',0)>0 ? round((1-_var($disk,'fsFree',0)/$disk['fsSize'])*100) : 0).'%' : false;
  if ($used) {
    if ($text==2 || $text==21) {
      $load = substr($used,0,-1);
      $critical = _var($disk,'critical')>=0 ? $disk['critical'] : (_var($display,'critical')>=0 ? $display['critical'] : 0);
      $warning = _var($disk,'warning')>=0 ? $disk['warning'] : (_var($display,'warning')>=0 ? $display['warning'] : 0);
      if ($critical>0 && $load>=$critical) {$class = 'redbar'; $full++;}
      elseif ($warning>0 && $load>=$warning) {$class = 'orangebar'; $high++;}
      else $class = 'greenbar';
    } else {
      $class = false;
    }
    return $text%10==0 ? $used : "<span class='load'>$used</span><div class='usage-disk sys'><span style='width:$used'".($class?" class='$class'":"")."></span><span></span></div>";
  } else {
    return $text%10==0 ? "-" : "<span class='load'>-</span><div class='usage-disk sys none'><span></span></div>";
  }
}

function array_group($type, $pool=false) {
  global $disks,$error,$warning,$red,$orange,$fail,$smart,$full,$high;
  if ($type != 'Data') {$error = $warning = $red = $orange = $fail = $smart = $full = $high = 0;}
  $echo = [];
  foreach ($disks as $disk) if (_var($disk,'type')==$type && ((_var($disk,'type')=='Parity') ? strpos(_var($disk,'status'),'DISK_NP')===false : strcmp(_var($disk,'status'),'DISK_NP')!=0) && (!$pool||$pool==prefix(_var($disk,'name')))) {
    $echo[] = "<tr class='updated'><td>";
    $echo[] = "<span class='w26'>".device_name($disk)."</span>";
    $echo[] = "<span class='w18'>".device_status($disk,$error,$warning)."</span>";
    $echo[] = "<span class='w18'>".device_temp($disk,$red,$orange)."</span>";
    $echo[] = "<span class='w18'>".device_smart($disk,$fail,$smart)."</span>";
    $echo[] = "<span class='w18'>".device_usage($disk,$full,$high)."</span>";
    $echo[] = "</td></tr>";
  }
  return implode($echo);
}

function extra_group() {
  global $devs,$error,$warning,$red,$orange,$fail,$smart,$full,$high;
  $error = $warning = $red = $orange = $fail = $smart = $full = $high = 0;
  $echo = [];
  foreach ($devs as $disk) {
    $name = _var($disk,'name');
    $disk['type'] = "Extra";
    $disk['color'] = _var($disk,'spundown')==0 ? 'blue-on' : 'blue-blink';
    $echo[] = "<tr class='updated'><td>";
    $echo[] = "<span class='w26'>".device_name($disk)."</span>";
    $echo[] = "<span class='w18'>".device_status($disk,$error,$warning)."</span>";
    $echo[] = "<span class='w18'>".device_temp($disk,$red,$orange)."</span>";
    $echo[] = "<span class='w18'>".device_smart($disk,$fail,$smart)."</span>";
    $echo[] = "<span class='w18'>".device_usage($disk,$full,$high)."</span>";
    $echo[] = "</td></tr>";
  }
  return implode($echo);
}

function update_translation($locale) {
  global $docroot,$language;
  $language = [];
  if ($locale) {
    $text = "$docroot/languages/$locale/translations.txt";
    if (file_exists($text)) {
      $store = "$docroot/languages/$locale/translations.dot";
      if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
      $language = unserialize(file_get_contents($store));
    }
    $text = "$docroot/languages/$locale/dashboard.txt";
    if (file_exists($text)) {
      $store = "$docroot/languages/$locale/dashboard.dot";
      if (!file_exists($store)) file_put_contents($store,serialize(parse_lang_file($text)));
      $language = array_merge($language,unserialize(file_get_contents($store)));
    }
  }
}

function create_sync($file) {
  return file_exists($file) ? explode(',',file_get_contents($file)) : [];
}

function print_error($error) {
  return sprintf(_('Finding **%s** error'.($error==1?'':'s')),$error?:'0');
}

while (true) {
  $var   = (array)@parse_ini_file("$varroot/var.ini");
  $devs  = (array)@parse_ini_file("$varroot/devs.ini",true);
  $disks = (array)@parse_ini_file("$varroot/disks.ini",true);
  $saved = (array)@parse_ini_file("$varroot/monitor.ini",true);
  $echo  = []; $p = 0;
  $size  = _var($var,'mdResyncSize',0);
  $spot  = _var($var,'mdResyncPos',0);
  require "$docroot/webGui/include/CustomMerge.php";
  require "$docroot/webGui/include/Preselect.php";
  // check for language changes
  extract(parse_plugin_cfg('dynamix',true));
  if (_var($display,'locale') != $locale_init) {
    $locale_init = _var($display,'locale');
    update_translation($locale_init);
  }
  //array devices
  $echo['disk'] = [array_group('Parity').array_group('Data'), $error+$warning, $red+$orange, $fail+$smart, $full+$high];

  //pool devices
  foreach (pools_filter($disks) as $pool) {
    if (empty($disks[$pool]['devices'])) continue;
    $echo['pool'][$p++] = [array_group('Cache',$pool), $error+$warning, $red+$orange, $fail+$smart, $full+$high];
  }
  //unassigned devices
  $echo['open'] = [extra_group(), $error+$warning, $red+$orange, $fail+$smart, $full+$high];

  // parity status
  $a = 'parity';
  $disks = parity_filter($disks);
  $parity_slots = count($disks);
  $parity_disabled = $parity_invalid = 0;
  foreach ($disks as $disk) {
    if (strpos(_var($disk,'status'),"DISK_NP")===0) $parity_disabled++;
    elseif (strpos(_var($disk,'status'),"DISK_INVALID")===0) $parity_invalid++;
  }
  if ($spot) {
    $number = _var($display,'number','.,');
    $action = preg_split('/\s+/',_var($var,'mdResyncAction'));
    switch ($action[0]) {
      case "recon": $mode = $action[1]=='P' ? 'Parity-Sync' : 'Data-Rebuild'; break;
      case "check": $mode = count($action)>1 ? 'Parity-Check' : 'Read-Check'; break;
      case "clear": $mode = 'Disk-Clear'; break;
      default     : $mode = ''; break;
    }
    $echo[$a] = "<span class='orange'>"._($mode).' '._('in progress').'... '._('Completed').': '.number_format($spot/($size/100+1),1,$number[0],$number[1])." %.</span>";
  } else {
    if ($parity_slots==$parity_disabled) {
      $echo[$a] = "<span class='red'>"._('Parity disk'.($parity_slots==1?'':'s')." not present")."</span>";
    } elseif ($parity_slots > $parity_invalid) {
      if ($parity_invalid==0) {
        $echo[$a] = "<span class='green'>"._('Parity is valid')."</span>";
      } else {
        $echo[$a] = "<span class='orange'>"._('Parity is degraded').": $parity_invalid "._('invalid device'.($parity_invalid==1?'':'s'))."</span>";
      }
    } else {
      if (empty($var['mdInvalidDisk'])) {
        $echo[$a] = "<span class='red strong'>"._('Parity is invalid')."</span>";
      } else {
        $echo[$a] = "<span class='red strong'>"._('Data is invalid')."</span>";
      }
    }
  }

  // parity schedule
  $a = 'schedule';
  [$delta,$bytes] = [_var($var,'mdResyncDt',0),_var($var,'mdResyncDb',0)];
  $synced = create_sync($stamps);
  $sbSynced = array_shift($synced) ?: _var($var,'sbSynced');
  $sbUpdate = $delta ? $sbSynced : _var($var,'sbUpdated');
  if ($spot) {
    $echo[$a][] = sprintf(_('Current operation %s on **%s**'),($delta?_('started'):_('paused')),_(my_time($sbUpdate).day_count($sbUpdate),0));
    $echo[$a][] = "<i class='fa fa-fw fa-clock-o'></i> "._('Elapsed time').": "._(my_clock(floor((time()-$sbSynced)/60)),2)."<br><i class='fa fa-fw fa-flag-checkered'></i> "._('Estimated finish').': '.($bytes ? _(my_clock(round(((($delta*(($size-$spot)/($bytes/100+1)))/100)/60),0)),2) : _('Unknown'))."<br><i class='fa fa-fw fa-search'></i> ".print_error(_var($var,'sbSyncErrs',0));
  } else {
    [$date,$duration,$speed,$status,$error,$action,$size] = last_parity_log();
    if (_var($var,'sbSyncExit',0)!=0) {
      $echo[$a][] = sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0));
      $echo[$a][] = "<br><i class='fa fa-fw fa-dot-circle-o'></i> "._('Error code').": ".my_error(_var($var,'sbSyncExit'));
      $echo[$a][] = "<br><i class='fa fa-fw fa-search'></i> ".print_error(_var($var,'sbSyncErrs',0));
    } elseif (_var($var,'sbSynced',0)==0) {
      if (!$date) {
        $echo[$a][] = _('Parity has not been checked yet');
      } elseif ($status==0) {
        $echo[$a][] = sprintf(_('Last checked on **%s**'),_(my_time($date).day_count($date),0));
        $echo[$a][] = "<br><i class='fa fa-fw fa-clock-o'></i> "._('Duration').": ".my_check($duration,$speed);
        $echo[$a][] = "<br><i class='fa fa-fw fa-search'></i> ".print_error($error);
      } else {
        $echo[$a][] = sprintf(_('Last check incomplete on **%s**'),_(my_time($date).day_count($date),0));
        $echo[$a][] = "<br><i class='fa fa-fw fa-dot-circle-o'></i> "._('Error code').": ".my_error($status);
        $echo[$a][] = "<br><i class='fa fa-fw fa-search'></i> ".print_error($error);
      }
    } elseif (_var($var,'sbSynced2',0)==0) {
      if ($status==0) {
        $echo[$a][] = sprintf(_('Last checked on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0));
        $echo[$a][] = "<br><i class='fa fa-fw fa-clock-o'></i> "._('Duration').": ".my_check($duration,$speed);
        $echo[$a][]= "<br><i class='fa fa-fw fa-search'></i> ".print_error($error);
      } else {
        $echo[$a][] = sprintf(_('Last check incomplete on **%s**'),_(my_time(_var($var,'sbSynced',0)).day_count(_var($var,'sbSynced',0)),0));
        $echo[$a][] = "<br><i class='fa fa-fw fa-dot-circle-o'></i> "._('Error code').": ".my_error($status);
        $echo[$a][] = "<br><i class='fa fa-fw fa-search'></i> ".print_error($error);
      }
    } else {
      $echo[$a][] = sprintf(_('Last check completed on **%s**'),_(my_time(_var($var,'sbSynced2',0)).day_count(_var($var,'sbSynced2',0)),0));
      $echo[$a][] = "<br><i class='fa fa-fw fa-clock-o'></i> "._('Duration').': '.my_check($duration,$speed);
      $echo[$a][] = "<br><i class='fa fa-fw fa-search'></i> ".print_error(_var($var,'sbSyncErrs',0));
    }
    [$m,$h] = explode(' ',$parity['hour']);
    $time = time();
    $check = true;
    switch ($parity['mode']) {
    case 0: // check disabled
      $check = false;
      break;
    case 1: // daily check
      $t = mktime($h,$m,0)-$time;
      if ($t < 0) $t += 86400;
      break;
    case 2: // weekly check
      $t = $parity['day']-date('w',$time);
      if ($t < 0) $t += 7;
      $t = mktime($h,$m,0)+$t*86400-$time;
      if ($t < 0) $t += 86400*7;
      break;
    case 3: // monthly check
      $D = $parity['dotm'];
      $M = date('n',$time);
      $Y = date('Y',$time);
      $last = ($D == '28-31');
      if ($last) $D = last_day();
      $t = mktime($h,$m,0,$M,$D,$Y)-$time;
      if ($t < 0) {
        if ($M < 12) $M++; else {$M = 1; $Y++;}
        if ($last) $D = last_day();
        $t = mktime($h,$m,0,$M,$D,$Y)-$time;
      }
      break;
    case 4: // yearly check
      $D = $parity['dotm'];
      $M = $parity['month'];
      $Y = date('Y',$time);
      $last = ($D == '28-31');
      if ($last) $D = last_day();
      $t = mktime($h,$m,0,$M,$D,$Y)-$time;
      if ($t < 0) {
        $Y++;
        if ($last) $D = last_day();
        $t = mktime($h,$m,0,$M,$D,$Y)-$time;
      }
      break;
    case 5: // custom check
      $days = explode(',',$parity['day']);
      $months = explode(',',$parity['month']);
      $today = date('w',$time);
      $date = date('n',$time);
      $D = find_day($today);
      $M = find_month($date);
      $Y = date('Y',$time);
      $now = $M==$date;
      if ($M < $date) $Y++;
      switch ($parity['dotm']) {
      case '*' : $t = stage(-1); break;
      case 'W1': $t = stage(1); break;
      case 'W2': $t = stage(8); break;
      case 'W3': $t = stage(15); break;
      case 'W4': $t = stage(22); break;
      case 'WL': $t = stage(0); break;}
      break;
    }
    if ($check) {
      $frmt = _var($display,'date').(_var($display,'date')!='%c' ? ", "._var($display,'time') : "");
      $extra = sprintf(_('Next check scheduled on **%s**'),_(my_date($frmt,$time+$t),0))."<br><i class='fa fa-fw fa-clock-o'></i> "._('Due in').": "._(my_clock(floor($t/60)),2);
    } else {
      $extra = _('Scheduled parity check is disabled');
    }
    $echo[$a] = [implode($echo[$a]), $extra];
  }

  $echo = json_encode($echo);
  $md5_new = md5($echo,true);
  if ($md5_new !== $md5_old) {
    $md5_old = publish('update2',$echo)!==false ? $md5_new : -1;
  }
  sleep(2);
}
?>
